package 多线程;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

//Java并发编程之ReentrantLock
//
//文章目录
//1、死锁
//2、活锁
//3、饥饿
//4、ReentrantLock
//4.1、基本语法
//4.2、支持锁重入
//4.3、可中断 (针对于lockInterruptibly()方法获得的中断锁)
//4.4、锁超时 (lock.tryLock())
//4.5、公平锁 new ReentrantLock(true)
//4.6、条件变量
//5、Lock和synchronized区别
//6、同步模式之顺序控制 (案例)
//6.1、wait/notify版本实现
//6.2、ReentrantLock的await/signal
//6.3、LockSupport中的park/unpart
//7、同步模式之交替控制 (案例)
//7.1、wait/notify版本实现
//7.2、await/signal版本
//7.3、park/unpark版本

//1、死锁
//一个线程需要同时获取多把锁，这时就容易发生死锁
//
//如：线程1获取A对象锁, 线程2获取B对象锁; 此时线程1又想获取B对象锁, 线程2又想获取A对象锁; 它们都等着对象释放锁, 此时就称为死锁:
//
//public static void main(String[] args) {
//	final Object A = new Object();
//	final Object B = new Object();
//	
//	new Thread(()->{
//		synchronized (A) {
//			try {
//				Thread.sleep(2000);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			synchronized (B) {
//
//			}
//		}
//	}).start();
//
//	new Thread(()->{
//		synchronized (B) {
//			try {
//				Thread.sleep(1000);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			synchronized (A) {
//
//			}
//		}
//	}).start();◾
//}

//2、活锁
//活锁出现在两个线程互相改变对方的结束条件，谁也无法结束
//
//死锁与活锁的区别:
//
//◾死锁是因为线程互相持有对象想要的锁，并且都不释放，最后到时线程阻塞，停止运行的现象。
//◾活锁是因为线程间修改了对方的结束条件，而导致代码一直在运行，却一直运行不完的现象。
//
//3、饥饿
//◾某些线程因为优先级太低，导致一直无法获得资源的现象
//◾在使用顺序加锁时，可能会出现饥饿现象
//
//4、ReentrantLock
//
//4.1、基本语法
/*
//获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();//这里可以是自己实现Lock接口的实现类，也可以是jdk提供的同步组件
//加锁
lock.lock();//一般不将锁的获取放在try语句块中，因为如果发生异常，在抛出异常的同时，也会导致锁的无故释放
try {
	//需要执行的代码
}finally {
	//释放锁
	lock.unlock();//放在finally代码块中，保证锁一定会被释放
}
*/

//Lock接口的方法:
//public interface Lock {
//
///**
// * 获取锁，调用该方法的线程会获取锁，当获取到锁之后会从该方法但会
// */
//void lock();
//
///**
// * 可响应中断。即在获取锁的过程中可以中断当前线程
// */
//void lockInterruptibly() throws InterruptedException;
//
///**
// * 尝试非阻塞的获取锁，调用该方法之后会立即返回，如果获取到锁就返回true否则返回false
// */
//boolean tryLock();
//
///**
// * 超时的获取锁，下面的三种情况会返回
// * ①当前线程在超时时间内获取到了锁
// * ②当前线程在超时时间内被中断
// * ③超时时间结束，返回false
// */
//boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//
///**
// * 释放锁
// */
//void unlock();
//
///**
// * 获取等待通知组件，该组件和当前锁绑定，当前线程只有获取到了锁才能调用组件的wait方法，调用该方法之后会释放锁
// */
//Condition newCondition();
//}

//相比于synchronized，Lock接口所具备的其他特性:
//①尝试非阻塞的获取锁tryLock()：当前线程尝试获取锁，如果该时刻锁没有被其他线程获取到，就能成功获取并持有锁
//
//②能被中断的获取锁lockInterruptibly()：获取到锁的线程能够响应中断，当获取到锁的线程被中断的时候，会抛出中断异常同时释放持有的锁
//
//③超时的获取锁tryLock(long time, TimeUnit unit)：在指定的截止时间获取锁，如果没有获取到锁返回false


//4.2、支持锁重入
//◾可重入锁是指同一个线程如果首次获得了这把锁，那么因为它是这把锁的拥有者，因此有权利再次获取这把锁
//◾如果是不可重入锁，那么第二次获得锁时，自己也会被锁挡住

//需解决：
//①线程再次获取锁的识别问题（锁需要识别当前要获取锁的线程是否为当前占有锁的线程）；
//②锁的释放（同一个线程多次获取同一把锁，那么锁的记录也会不同。一般来说，当同一个线程重复n次获取锁之后，只有在之后的释放n次锁之后，其他的线程才能去竞争
//这把锁）

/*public class 使用ReentrantLock1 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        // 如果有竞争就进入`阻塞队列`, 一直等待着,不能被打断
        lock.lock();
        try {
            System.out.println("entry main...");
            m1();
        } finally {
            lock.unlock();
        }
        System.out.println("main end");
    }
    private static void m1() {
        lock.lock();
        try {
            System.out.println("entry m1...");
            m2();
        } finally {
            lock.unlock();
        }
    }
    private static void m2() {
        System.out.println("entry m2....");
    }
    
}*/

//输出结果：
//
//entry main...
//entry m1...
//entry m2....
//main end
//
//4.3、可中断 (针对于lockInterruptibly()方法获得的中断锁)
//◾synchronized 和 reentrantlock.lock() 的锁, 是不可被打断的; 也就是说别的线程已经获得了锁, 我的线程就需要一直等待下去. 不能中断
//◾可被中断的锁, 通过lock.lockInterruptibly()获取的锁对象, 可以通过调用阻塞线程的interrupt()方法打断
//◾如果某个线程处于阻塞状态，可以调用其interrupt方法让其停止阻塞，获得锁失败
//◾处于阻塞状态的线程，被打断了就不用阻塞了，直接停止运行
//◾可中断的锁, 在一定程度上可以被动的减少死锁的概率, 之所以被动, 是因为我们需要手动调用阻塞线程的interrupt方法;
//使用lock.lockInterruptibly()可以从阻塞队列中打断
//
/*public class 使用ReentrantLock1 {
	private static final ReentrantLock lock = new ReentrantLock();

	public static void main(String[] args) {
		Thread t1 = new Thread(() -> {
			System.out.println("t1线程启动...");
			try {
				// lockInterruptibly()是一个可打断的锁, 如果有锁竞争在进入阻塞队列后,可以通过interrupt进行打断
				lock.lockInterruptibly();
				System.out.println("t1--------");
			} catch (InterruptedException e) {
				e.printStackTrace();
				System.out.println("等锁的过程中被打断"); // 没有获得锁就被打断跑出的异常
				return;
			}
			try {
				System.out.println("t1线程获得了锁");
			} finally {
				lock.unlock();
			}
		}, "t1");
		// 主线程获得锁(此锁不可打断)
		lock.lock();
		System.out.println("main线程获得了锁");
		// 启动t1线程
		t1.start();
		try {
			try {
				//TimeUnit.SECONDS.sleep(1);
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			t1.interrupt(); // 打断t1线程
			System.out.println("执行打断");
		} finally {
			lock.unlock();
		}
	}
}*/
//
//使用lock.lock()不可以从阻塞队列中打断, 一直等待别的线程释放锁
//
/*public class 使用ReentrantLock1 {
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1线程启动...");
            lock.lock();
            try {
                System.out.println("t1线程获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        // 主线程获得锁(此锁不可打断)
        lock.lock();
        System.out.println("main线程获得了锁");
        // 启动t1线程
        t1.start();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.interrupt();            //打断t1线程
            System.out.println("main线程执行打断");
        } finally {
            lock.unlock();
        }
    }
}*/
//
//lock()锁不能被打断, 在主线程中调用t1.interrupt(),t1没被打断， 当主线程释放锁之后, t1获得了锁
//
//4.4、锁超时 (lock.tryLock())
//◾使用 lock.tryLock() 方法会返回获取锁是否成功。如果成功则返回true，反之则返回false
//◾并且tryLock方法可以设置指定等待时间，参数为：tryLock(long timeout, TimeUnit unit) , 其中timeout为最长等待时间，TimeUnit为时间单位
//◾获取锁的过程中, 如果超过等待时间, 或者被打断, 就直接从阻塞队列移除, 此时获取锁就失败了, 不会一直阻塞着 ! (可以用来实现死锁问题)
//不设置等待时间, 立即失败
//
/*public class 使用ReentrantLock1 {
	private static final ReentrantLock lock = new ReentrantLock();

	public static void main(String[] args) {
		Thread t1 = new Thread(() -> {
			System.out.println("尝试获得锁");
			// 此时肯定获取失败, 因为主线程已经获得了锁对象
			if (!lock.tryLock()) {
				System.out.println("获取立刻失败，返回");
				return;
			}
			try {
				System.out.println("获得到锁");
			} finally {
				lock.unlock();
			}
		}, "t1");

		lock.lock();
		System.out.println("获得到锁");
		t1.start();
		// 主线程2s之后才释放锁
		try {
			Thread.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("释放了锁");
		lock.unlock();
	}
}*/
//
//设置等待时间, 超过等待时间还没有获得锁, 失败, 从阻塞队列移除该线程
//
/*public class 使用ReentrantLock1 {
    private static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("尝试获得锁");
            try {
                // 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    System.out.println("获取锁超时，返回");
                    return;
                }
                System.out.println(lock.toString());//t1获得lock
            } catch (InterruptedException e) {
                System.out.println("被打断了, 获取锁失败, 返回");
                e.printStackTrace();
                return;
            }
            try {
                System.out.println(Thread.currentThread().getName()+"获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获得到锁");
        t1.start();
//        t1.interrupt();
        // 主线程2s之后才释放锁
        System.out.println(lock.toString());
        Thread.sleep(100);//调整睡眠时间，可观察t1获得锁的情况。
        System.out.println("main线程释放了锁");
        lock.unlock();
    }
}*/
//
//4.5、公平锁 new ReentrantLock(true)
//◾ReentrantLock默认是非公平锁, 可以指定为公平锁
//◾在线程获取锁失败，进入阻塞队列时，先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。一般不设置ReentrantLock为公平的, 会降低并发度
//◾Synchronized底层的Monitor锁就是不公平的, 和谁先进入阻塞队列是没有关系的
////默认是不公平锁，需要在创建时指定为公平锁
//ReentrantLock lock = new ReentrantLock(true);
//
//公平锁 (new ReentrantLock(true))
//◾公平锁, 把竞争的线程放在一个先进先出的阻塞队列上
//◾只要持有锁的线程执行完了, 唤醒阻塞队列中的下一个线程获取锁即可; 此时先进入阻塞队列的线程先获取到锁

//非公平锁 (synchronized, new ReentrantLock())
//◾非公平锁, 当阻塞队列中已经有等待的线程A了, 此时后到的线程B, 先去尝试看能否获得到锁对象.
//◾如果获取成功, 此时就不需要进入阻塞队列了. 这样以来后来的线程B就先活的到锁了

//4.6、条件变量
//◾synchronized 中也有条件变量，就是Monitor监视器中的 waitSet等待集合，当条件不满足时进入waitSet 等待
//◾reentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量
//◾这就好比synchronized 是那些不满足条件的线程都在一间休息室等通知; (此时会造成虚假唤醒), 而 ReentrantLock 支持多间休息室，有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒; (可以避免虚假唤醒)

//等待唤醒：
//◾await 前需要 获得锁
//◾await 执行后，会释放锁，进入 conditionObject (条件变量)中等待
//◾await 的线程被唤醒（或打断、或超时）取重新竞争 lock 锁，竞争 lock 锁成功后，从 await 后继续执行
//◾signal 方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
//◾signalAll方法, 唤醒条件变量(休息室)中的所有线程

//public class ConditionVariable {
public class 使用ReentrantLock1 {
    private static boolean hasCigarette = false;
    private static boolean hasTakeout = false;
    private static final ReentrantLock lock = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = lock.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutSet = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"：有烟没？[{"+hasCigarette+"}]");
                while (!hasCigarette) {
                    System.out.println(Thread.currentThread().getName()+"：没烟，先歇会！");
                    try {
                        // 此时小南进入到 等烟的休息室
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"：烟来咯, 可以开始干活了"+lock.toString());
            } finally {
                lock.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"：外卖送到没？[{"+hasTakeout+"}]");
                while (!hasTakeout) {
                    System.out.println(Thread.currentThread().getName()+"：没外卖，先歇会！");
                    try {
                        // 此时小女进入到 等外卖的休息室
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"：外卖来咯, 可以开始干活了"+lock.toString());
            } finally {
                lock.unlock();
            }
        }, "小女").start();
        TimeUnit.SECONDS.sleep(1);
        //Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"：送外卖的来咯~");
                hasTakeout = true;
                // 唤醒等外卖的小女线程
                waitTakeoutSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送外卖的").start();
        TimeUnit.SECONDS.sleep(1);
        //Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"：送烟的来咯~");
                hasCigarette = true;
                // 唤醒等烟的小南线程
                waitCigaretteSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送烟的").start();
    }
}
//
//
//输出结果：
//
//小南：有烟没？[{false}]
//小南：没烟，先歇会！
//小女：外卖送到没？[{false}]
//小女：没外卖，先歇会！
//送外卖的：送外卖的来咯~
//小女：外卖来咯, 可以开始干活了
//送烟的：送烟的来咯~
//小南：烟来咯, 可以开始干活了
//
//Process finished with exit code 0
//
//5、Lock和synchronized区别
//
//
//6、同步模式之顺序控制 (案例)
//假如有两个线程, 线程A打印1, 线程B打印2
//要求: 程序先打印2, 再打印1
//6.1、wait/notify版本实现
//设置标记，fasle表示：2没打印就一直等待
//另外线程打印2并将标记设置为true
//public class SyncPrintWaitTest {
//    public static final Object lock = new Object();
//    // t2线程释放执行过
//    public static boolean t2IsRunned = false;
//    public static void main(String[] args) {
//        Thread t1 = new Thread(() -> {
//            synchronized (lock) {
//                while (!t2IsRunned) {
//                    try {
//                        // 进入等待(waitset), 会释放锁
//                        lock.wait();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                }
//                System.out.println("1");
//            }
//        }, "t1");
//
//        Thread t2 = new Thread(() -> {
//            synchronized (lock) {
//                System.out.println("2");
//                t2IsRunned = true;
//                lock.notify();
//            }
//        }, "t2");
//
//        t1.start();
//        t2.start();
//    }
//}
//
//6.2、ReentrantLock的await/signal
//public class SyncPrintWaitTest {
//    public static final Lock lock = new ReentrantLock();
//    public static Condition condition = lock.newCondition();
//    // t2线程释放执行过
//    public static boolean t2IsRunned = false;
//    public static void main(String[] args) {
//        Thread t1 = new Thread(() -> {
//            lock.lock();
//            try {
//                while (!t2IsRunned) {
//                    try {
//                        condition.await();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                }
//                System.out.println("1");
//            }finally {
//                lock.unlock();
//            }
//        }, "t1");
//
//        Thread t2 = new Thread(() -> {
//            lock.lock();
//            try {
//                System.out.println("2");
//                t2IsRunned = true;
//                condition.signal();
//            } finally {
//                lock.unlock();
//            }
//        }, "t2");
//
//        t1.start();
//        t2.start();
//    }
//}
//
//6.3、LockSupport中的park/unpart
//park()：暂停t1线程
//unpark(t1)：恢复执行t1线程
//public class SyncPrintWaitTest {
//    public static void main(String[] args) {
//        Thread t1 = new Thread(() -> {
//            LockSupport.park();
//            System.out.println("1");
//        }, "t1");
//        t1.start();
//
//        new Thread(() -> {
//            System.out.println("2");
//            LockSupport.unpark(t1);
//        }, "t2").start();
//    }
//}
//
//7、同步模式之交替控制 (案例)
//线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次
//现在要求输出 abcabcabcabcabcabc
//7.1、wait/notify版本实现
//new WaitNotify(1, 5)；初始化flag为1
//waitFlag不为1的线程进入等待
//waitFlag为1，与flag相等则打印1，并赋值flag为2
//下轮则打印2依次类推
//public class TestWaitNotify {
//    public static void main(String[] args) {
//        WaitNotify waitNotify = new WaitNotify(1, 5);
//        
//        new Thread(() -> {
//            waitNotify.print("a", 1, 2);
//        }, "a线程").start();
//        
//        new Thread(() -> {
//            waitNotify.print("b", 2, 3);
//        }, "b线程").start();
//        
//        new Thread(() -> {
//            waitNotify.print("c", 3, 1);
//        }, "c线程").start();
//    }
//}
//
//@Data
//@AllArgsConstructor
//class WaitNotify {
//    private int flag;
//    // 循环次数
//    private int loopNumber;
//    /*
//        输出内容    等待标记    下一个标记
//        a           1          2
//        b           2          3
//        c           3          1
//     */
//    public void print(String str, int waitFlag, int nextFlag) {
//        for (int i = 0; i < loopNumber; i++) {
//            synchronized (this) {
//                while (waitFlag != this.flag) {
//                    try {
//                        this.wait();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                }
//                System.out.print(str);
//                this.flag = nextFlag;
//                this.notifyAll();
//            }
//        }
//    }
//}
//
//7.2、await/signal版本
//设置三个等待集合，启动三个线程分别在abc等待集合中等待
//先唤醒a线程，打印a，并且唤醒b
//依次类推
//public class TestAwaitSignal {
//    public static void main(String[] args) throws InterruptedException {
//        AwaitSignal awaitSignal = new AwaitSignal(5);
//        Condition a_condition = awaitSignal.newCondition();
//        Condition b_condition = awaitSignal.newCondition();
//        Condition c_condition = awaitSignal.newCondition();
//        
//        new Thread(() -> {
//            awaitSignal.print("a", a_condition, b_condition);
//        }, "a").start();
//
//        new Thread(() -> {
//            awaitSignal.print("b", b_condition, c_condition);
//        }, "b").start();
//
//        new Thread(() -> {
//            awaitSignal.print("c", c_condition, a_condition);
//        }, "c").start();
//
//        awaitSignal.lock();
//        try {
//            a_condition.signal();  //首先唤醒a线程
//        } finally {
//            awaitSignal.unlock();
//        }
//    }
//}
//
//class AwaitSignal extends ReentrantLock {
//    private final int loopNumber;
//    public AwaitSignal(int loopNumber) {
//        this.loopNumber = loopNumber;
//    }
//    public void print(String str, Condition condition, Condition next) {
//        for (int i = 0; i < loopNumber; i++) {
//            lock();
//            try {
//                try {
//                    condition.await();
//                    System.out.print(str);
//                    next.signal();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            } finally {
//                unlock();
//            }
//        }
//    }
//}
//
//
//7.3、park/unpark版本
//public class TestParkUnpark {
//    static Thread a;
//    static Thread b;
//    static Thread c;
//
//    public static void main(String[] args) {
//        ParkUnpark parkUnpark = new ParkUnpark(5);
//
//        a = new Thread(() -> {
//            parkUnpark.print("a", b);
//        }, "a");
//
//        b = new Thread(() -> {
//            parkUnpark.print("b", c);
//        }, "b");
//
//        c = new Thread(() -> {
//            parkUnpark.print("c", a);
//        }, "c");
//
//        a.start();
//        b.start();
//        c.start();
//        LockSupport.unpark(a);
//    }
//}
//
//class ParkUnpark {
//    private final int loopNumber;
//
//    public ParkUnpark(int loopNumber) {
//        this.loopNumber = loopNumber;
//    }
//
//    public void print(String str, Thread nextThread) {
//        for (int i = 0; i < loopNumber; i++) {
//            LockSupport.park();
//            System.out.print(str);
//            LockSupport.unpark(nextThread);
//        }
//    }
//}
